﻿using System;
using System.Collections.Generic;
using WSKnowledge.WSHelpers.DesignByContract;

namespace WSKnowledge.WSHelpers
{
	public class Weekdays : IEquatable<Weekdays>
	{
		private const byte Saturday = 1;
		private const byte Sunday = 2;
		private const byte Monday = 4;
		private const byte Tuesday = 8;
		private const byte Wednesday = 16;
		private const byte Thursday = 32;
		private const byte Friday = 64;

		private byte _asByte;
		private readonly List<DayOfWeek> _asCollection;

		private static byte ConvertToByte(DayOfWeek day)
		{
			switch (day)
			{
				case DayOfWeek.Saturday:
					return Saturday;

				case DayOfWeek.Sunday:
					return Sunday;

				case DayOfWeek.Monday:
					return Monday;

				case DayOfWeek.Tuesday:
					return Tuesday;

				case DayOfWeek.Wednesday:
					return Wednesday;

				case DayOfWeek.Thursday:
					return Thursday;

				case DayOfWeek.Friday:
					return Friday;
			}

			throw new ArgumentException("Could not convert DayOfWeek to the appropriate Byte form.");
		}

		public Weekdays()
		{
			_asByte = 0x0;
			_asCollection = new List<DayOfWeek>();
		}

		public Weekdays(Byte input)
			: this()
		{
			Check.Require(input >= 0 && input <= 127, "Invalid Byte representation of Weekdays collection.");
			if ((input | Saturday) == input) Select(DayOfWeek.Saturday);
			if ((input | Sunday) == input) Select(DayOfWeek.Sunday);
			if ((input | Monday) == input) Select(DayOfWeek.Monday);
			if ((input | Tuesday) == input) Select(DayOfWeek.Tuesday);
			if ((input | Wednesday) == input) Select(DayOfWeek.Wednesday);
			if ((input | Thursday) == input) Select(DayOfWeek.Thursday);
			if ((input | Friday) == input) Select(DayOfWeek.Friday);
		}

		public Weekdays(params DayOfWeek[] dayOfWeeks)
			: this()
		{
			_asCollection = new List<DayOfWeek>();

			foreach (var dayOfWeek in dayOfWeeks)
				if (!IsSelected(dayOfWeek)) Select(dayOfWeek);
		}

		public bool IsSelected(DayOfWeek dayOfWeek)
		{
			if (_asByte == 0) return false; // nothing is selected

			return ((ConvertToByte(dayOfWeek) | _asByte) == _asByte);
		}

		public void Select(DayOfWeek dayOfWeek)
		{
			Check.Require(!IsSelected(dayOfWeek), "Weekday IS already selected.");
			_asByte |= ConvertToByte(dayOfWeek);
			_asCollection.Add(dayOfWeek);
		}

		public void Deselect(DayOfWeek dayOfWeek)
		{
			Check.Require(IsSelected(dayOfWeek), "Weekday is NOT selected.");
			_asByte ^= ConvertToByte(dayOfWeek);
			_asCollection.Remove(dayOfWeek);
		}

		public byte GetAsByte()
		{
			var ouput = _asByte;
			return ouput;
		}

		public IList<DayOfWeek> GetAsList()
		{
			return _asCollection.AsReadOnly();
		}

		public DayOfWeek[] GetAsArray()
		{
			return _asCollection.ToArray();
		}

		public override bool Equals(object obj)
		{
			return Equals(obj as Weekdays);
		}

		public bool Equals(Weekdays other)
		{
			if (ReferenceEquals(null, other)) return false;
			if (ReferenceEquals(this, other)) return true;
			return other.GetAsByte() == _asByte;
		}

		public override int GetHashCode()
		{
			return _asByte.GetHashCode();
		}

		public static bool operator ==(Weekdays left, Weekdays right)
		{
			return Equals(left, right);
		}

		public static bool operator !=(Weekdays left, Weekdays right)
		{
			return !Equals(left, right);
		}

		public static implicit operator Byte(Weekdays input)
		{
			return input == null ? (byte)0x0 : input.GetAsByte();
		}

		public static implicit operator Weekdays(Byte input)
		{
			return new Weekdays(input);
		}
	}
}
